#define CHAR_ENTER 10
#define CHAR_ESPACO 32
#define STR_V 0
#define STR_VT 1
#define STR_VN 2
#define STR_F 3
#define V_VN_VT 0
#define V_VN 1
#define V_VT 2
#define V_SOLO 3

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

void PulaLinha(FILE* pFile)
{
	//Pula linha testando a partir do próximo caracter.
	char buffer_char;
	do {
		buffer_char = fgetc(pFile);
	} while((buffer_char != CHAR_ENTER) && (buffer_char != EOF));
}

int strrec(char* str)
{
	switch(str[0])
	{
		case 'v':
			switch(str[1])
			{
				case 't':
					return STR_VT;
					break;

				case 'n':
					return STR_VN;
					break;

				case '\0':
					return STR_V;
					break;

				default:
					return -1;
					break;
			}

		case 'f':
			return STR_F;
			break;

		default:
			return -1;
			break;
	}
}

void MakeVector(float* point1, float* point2, float* vector)
{
	vector[0] = point2[0] - point1[0];
	vector[1] = point2[1] - point1[1];
	vector[2] = point2[2] - point1[2];
}

void ProdVet(float* vetor1, float* vetor2, float* vetor)
{
	vetor[0] = vetor1[1]*vetor2[2] - vetor1[2]*vetor2[1];
	vetor[1] = vetor1[2]*vetor2[0] - vetor1[0]*vetor2[2];
	vetor[2] = vetor1[0]*vetor2[1] - vetor1[1]*vetor2[0];
}

// Analisa oposição de vetores. (ângulo entre eles, maior ou menor que 90 graus)
int VetOpo(float* vetor1, float* vetor2)
{
	float vetor0[3],modulo0,modulo1,modulo2;
	vetor0[0] = vetor1[0] + vetor2[0];
	vetor0[1] = vetor1[1] + vetor2[1];
	vetor0[2] = vetor1[2] + vetor2[2];
	modulo0 = sqrt(pow(vetor0[0],2) + pow(vetor0[1],2) + pow(vetor0[2],2));
	modulo1 = sqrt(pow(vetor1[0],2) + pow(vetor1[1],2) + pow(vetor1[2],2));
	modulo2 = sqrt(pow(vetor2[0],2) + pow(vetor2[1],2) + pow(vetor2[2],2));
	if(modulo0>modulo1 && modulo0>modulo2)
		return 0;
	else
		return 1;
}

void ObjLoader(float** in_vertex_points, int* vertex_vector_size, float** in_texture_points, int* texture_vector_size, const char* nome_arquivo)
{
	int quant_v=0, quant_vt=0, quant_vn=0, quant_f=0;
	int cont_v=0, cont_vt=0, cont_vn=0, cont_f=0, cont_v_out=0, cont_t_out=0;
	float *vertex_points, *texture_points, *normal_vectors;
	float *out_vertex_points, *out_texture_points;
	int tipo_f;
	float ponto1[3],ponto2[3],ponto3[3];
	float vetor1[3],vetor2[3],vetor3[3],vetor_normal[3];
	int fnum[9];
	FILE* objFile;
	char tipo[2];
	int aux1,aux2,aux3;
	
	objFile = fopen(nome_arquivo,"r");

	// Calcula tamanho dos vetores de floats
	while(!feof(objFile))
	{
		fscanf(objFile,"%s",tipo);

		switch(strrec(tipo))
		{
			case STR_V: // Se a linha é do tipo v
				quant_v++;
				break;

			case STR_VT: // Se a linha é do tipo vt
				quant_vt++;
				break;

			case STR_VN: // Se a linha é do tipo vn
				quant_vn++;
				break;

			case STR_F: // Se a linha é do tipo f
				quant_f++;
				break;
		}
		
		PulaLinha(objFile); // Pula para próxima linha
	}

	// Aloca vetores de float
	vertex_points = (float*)malloc(sizeof(float)*quant_v*3);
	texture_points = (float*)malloc(sizeof(float)*quant_vt*2);
	normal_vectors = (float*)malloc(sizeof(float)*quant_vn*3);

	// Verifica quais tipos de informação o obj possui (vertices, texturas e normais) e aloca vetores finais
	if(quant_vt!=0 && quant_vn!=0)
	{
		tipo_f = V_VN_VT;
		out_vertex_points = (float*)malloc(sizeof(float)*9*quant_f); // Tamanho minimo do vetor final de vértices é estipulado como 9 (3 pontos de 3 dimensões) vezes a quantidade de linhas f
		out_texture_points = (float*)malloc(sizeof(float)*6*quant_f); // Tamanho minimo do vetor final de pontos de textura é estipulado como 6 (3 pontos de 2 dimensões) vezes quantidade de f
	}
	else if(quant_vn!=0)
	{
		tipo_f = V_VN;
		out_vertex_points = (float*)malloc(sizeof(float)*9*quant_f); // Tamanho minimo do vetor final de vértices é estipulado como 9 (3 pontos de 3 dimensões) vezes a quantidade de linhas f
	}
	else if(quant_vt!=0)
	{
		tipo_f = V_VT;
		out_vertex_points = (float*)malloc(sizeof(float)*9*quant_f); // Tamanho minimo do vetor final de vértices é estipulado como 9 (3 pontos de 3 dimensões) vezes a quantidade de linhas f
		out_texture_points = (float*)malloc(sizeof(float)*6*quant_f); // Tamanho minimo do vetor final de pontos de textura é estipulado como 6 (3 pontos de 2 dimensões) vezes quantidade de f
	}
	else
	{
		tipo_f = V_SOLO;
		out_vertex_points = (float*)malloc(sizeof(float)*9*quant_f); // Tamanho minimo do vetor final de vértices é estipulado como 9 (3 pontos de 3 dimensões) vezes a quantidade de linhas f
	}

	rewind(objFile);

	// Lẽ dados do arquivo
	while(!feof(objFile))
	{
		fscanf(objFile,"%s",tipo);

		switch(strrec(tipo))
		{
			case STR_V: // Lê linha do tipo v
				fscanf(objFile,"%f %f %f",&vertex_points[0+cont_v],&vertex_points[1+cont_v],&vertex_points[2+cont_v]);
				cont_v+=3;
				break;

			case STR_VT: // Lê linha do tipo vt
				fscanf(objFile,"%f %f",&texture_points[0+cont_vt],&texture_points[1+cont_vt]);
				cont_vt+=2;
				break;

			case STR_VN: // Lê linha do tipo vn
				fscanf(objFile,"%f %f %f",&normal_vectors[0+cont_vn],&normal_vectors[1+cont_vn],&normal_vectors[2+cont_vn]);
				cont_vn+=3;
				break;

			case STR_F: // Lê linha do tipo f
				switch(tipo_f)
				{
					case V_VN_VT: // Caso todos os tipos presentes...
						// Captura linha f e armazena em num.
						fscanf(objFile,"%d/%d/%d %d/%d/%d %d/%d/%d",&fnum[0],&fnum[1],&fnum[2],&fnum[3],&fnum[4],&fnum[5],&fnum[6],&fnum[7],&fnum[8]);

						// Atribui vetor normal do plano (triangulo)
						//aux1 = 
						vetor_normal[0] = normal_vectors[(fnum[2]-1)*3] + normal_vectors[(fnum[5]-1)*3] + normal_vectors[(fnum[8]-1)*3];
						vetor_normal[1] = normal_vectors[(fnum[2]-1)*3+1] + normal_vectors[(fnum[5]-1)*3+1] + normal_vectors[(fnum[8]-1)*3+1];
						vetor_normal[2] = normal_vectors[(fnum[2]-1)*3+2] + normal_vectors[(fnum[5]-1)*3+2] + normal_vectors[(fnum[8]-1)*3+2];

						// Atribui pontos do plano (triangulo)
						ponto1[0] = vertex_points[(fnum[0]-1)*3];
						ponto1[1] = vertex_points[(fnum[0]-1)*3+1];
						ponto1[2] = vertex_points[(fnum[0]-1)*3+2];
						ponto2[0] = vertex_points[(fnum[3]-1)*3];
						ponto2[1] = vertex_points[(fnum[3]-1)*3+1];
						ponto2[2] = vertex_points[(fnum[3]-1)*3+2];
						ponto3[0] = vertex_points[(fnum[6]-1)*3];
						ponto3[1] = vertex_points[(fnum[6]-1)*3+1];
						ponto3[2] = vertex_points[(fnum[6]-1)*3+2];
						
						// Atribui normal efetiva do plano (triangulo)
						MakeVector(ponto1,ponto2,vetor1);
						MakeVector(ponto1,ponto3,vetor2);
						ProdVet(vetor1,vetor2,vetor3);

						// Atribuição ordenada dos pontos da textura conforme os vértices dos triangulos.
						// Se a normal calculada não é oposta à normal do obj, inverte a ordem de atribuição.
						if(VetOpo(vetor1,vetor_normal))
						{
							out_vertex_points[cont_v_out] = ponto1[0];
							out_vertex_points[cont_v_out+1] = ponto1[1];
							out_vertex_points[cont_v_out+2] = ponto1[2];
							out_vertex_points[cont_v_out+3] = ponto2[0];
							out_vertex_points[cont_v_out+4] = ponto2[1];
							out_vertex_points[cont_v_out+5] = ponto2[2];
							out_vertex_points[cont_v_out+6] = ponto3[0];
							out_vertex_points[cont_v_out+7] = ponto3[1];
							out_vertex_points[cont_v_out+8] = ponto3[2];

							out_texture_points[cont_t_out] = texture_points[(fnum[1]-1)*2];
							out_texture_points[cont_t_out+1] = texture_points[(fnum[1]-1)*2+1];
							out_texture_points[cont_t_out+2] = texture_points[(fnum[4]-1)*2];
							out_texture_points[cont_t_out+3] = texture_points[(fnum[4]-1)*2+1];
							out_texture_points[cont_t_out+4] = texture_points[(fnum[7]-1)*2];
							out_texture_points[cont_t_out+5] = texture_points[(fnum[7]-1)*2+1];
						}
						else
						{
							out_vertex_points[cont_v_out] = ponto1[0];
							out_vertex_points[cont_v_out+1] = ponto1[1];
							out_vertex_points[cont_v_out+2] = ponto1[2];
							out_vertex_points[cont_v_out+3] = ponto3[0];
							out_vertex_points[cont_v_out+4] = ponto3[1];
							out_vertex_points[cont_v_out+5] = ponto3[2];
							out_vertex_points[cont_v_out+6] = ponto2[0];
							out_vertex_points[cont_v_out+7] = ponto2[1];
							out_vertex_points[cont_v_out+8] = ponto2[2];

							out_texture_points[cont_t_out] = texture_points[(fnum[1]-1)*2];
							out_texture_points[cont_t_out+1] = texture_points[(fnum[1]-1)*2+1];
							out_texture_points[cont_t_out+2] = texture_points[(fnum[7]-1)*2];
							out_texture_points[cont_t_out+3] = texture_points[(fnum[7]-1)*2+1];
							out_texture_points[cont_t_out+4] = texture_points[(fnum[4]-1)*2];
							out_texture_points[cont_t_out+5] = texture_points[(fnum[4]-1)*2+1];
						}
						cont_v_out+=9;
						cont_t_out+=6;
						break;

					case V_VN: // Caso vt ausente...
						// Captura linha f e armazena em num.
						fscanf(objFile,"%d//%d %d//%d %d//%d",&fnum[0],&fnum[1],&fnum[2],&fnum[3],&fnum[4],&fnum[5]);

						// Atribui vetor normal do plano (triangulo)
						vetor_normal[0] = normal_vectors[(fnum[1]-1)*3] + normal_vectors[(fnum[3]-1)*3] + normal_vectors[(fnum[5]-1)*3];
						vetor_normal[1] = normal_vectors[(fnum[1]-1)*3+1] + normal_vectors[(fnum[3]-1)*3+1] + normal_vectors[(fnum[5]-1)*3+1];
						vetor_normal[2] = normal_vectors[(fnum[1]-1)*3+2] + normal_vectors[(fnum[3]-1)*3+2] + normal_vectors[(fnum[5]-1)*3+2];

						// Atribui pontos do plano (triangulo)
						ponto1[0] = vertex_points[(fnum[0]-1)*3];
						ponto1[1] = vertex_points[(fnum[0]-1)*3+1];
						ponto1[2] = vertex_points[(fnum[0]-1)*3+2];
						ponto2[0] = vertex_points[(fnum[2]-1)*3];
						ponto2[1] = vertex_points[(fnum[2]-1)*3+1];
						ponto2[2] = vertex_points[(fnum[2]-1)*3+2];
						ponto3[0] = vertex_points[(fnum[4]-1)*3];
						ponto3[1] = vertex_points[(fnum[4]-1)*3+1];
						ponto3[2] = vertex_points[(fnum[4]-1)*3+2];

						// Atribui normal efetiva do plano (triangulo)
						MakeVector(ponto1,ponto2,vetor1);
						MakeVector(ponto1,ponto3,vetor2);
						ProdVet(vetor1,vetor2,vetor3);

						// Atribuição ordenada dos vértices dos triangulos.
						// Se a normal calculada não é oposta à normal do obj, inverte a ordem de atribuição.
						if(VetOpo(vetor1,vetor_normal))
						{
							out_vertex_points[cont_v_out] = ponto1[0];
							out_vertex_points[cont_v_out+1] = ponto1[1];
							out_vertex_points[cont_v_out+2] = ponto1[2];
							out_vertex_points[cont_v_out+3] = ponto2[0];
							out_vertex_points[cont_v_out+4] = ponto2[1];
							out_vertex_points[cont_v_out+5] = ponto2[2];
							out_vertex_points[cont_v_out+6] = ponto3[0];
							out_vertex_points[cont_v_out+7] = ponto3[1];
							out_vertex_points[cont_v_out+8] = ponto3[2];
						}
						else
						{
							out_vertex_points[cont_v_out] = ponto1[0];
							out_vertex_points[cont_v_out+1] = ponto1[1];
							out_vertex_points[cont_v_out+2] = ponto1[2];
							out_vertex_points[cont_v_out+3] = ponto3[0];
							out_vertex_points[cont_v_out+4] = ponto3[1];
							out_vertex_points[cont_v_out+5] = ponto3[2];
							out_vertex_points[cont_v_out+6] = ponto2[0];
							out_vertex_points[cont_v_out+7] = ponto2[1];
							out_vertex_points[cont_v_out+8] = ponto2[2];
						}
						cont_v_out+=9;
						break;

					case V_VT: // Caso vn ausente...
						// Captura linha f e armazena em num.
						fscanf(objFile,"%d/%d %d/%d %d/%d",&fnum[0],&fnum[1],&fnum[2],&fnum[3],&fnum[4],&fnum[5]);

						// Atribui pontos do plano (triangulo)
						ponto1[0] = vertex_points[(fnum[0]-1)*3];
						ponto1[1] = vertex_points[(fnum[0]-1)*3+1];
						ponto1[2] = vertex_points[(fnum[0]-1)*3+2];
						ponto2[0] = vertex_points[(fnum[2]-1)*3];
						ponto2[1] = vertex_points[(fnum[2]-1)*3+1];
						ponto2[2] = vertex_points[(fnum[2]-1)*3+2];
						ponto3[0] = vertex_points[(fnum[4]-1)*3];
						ponto3[1] = vertex_points[(fnum[4]-1)*3+1];
						ponto3[2] = vertex_points[(fnum[4]-1)*3+2];

						// Atribui normal efetiva do plano (triangulo)
						MakeVector(ponto1,ponto2,vetor1);
						MakeVector(ponto1,ponto3,vetor2);
						ProdVet(vetor1,vetor2,vetor3);

						// Atribuição ordenada dos pontos da textura conforme os vértices dos triangulos.
						out_vertex_points[cont_v_out] = ponto1[0];
						out_vertex_points[cont_v_out+1] = ponto1[1];
						out_vertex_points[cont_v_out+2] = ponto1[2];
						out_vertex_points[cont_v_out+3] = ponto2[0];
						out_vertex_points[cont_v_out+4] = ponto2[1];
						out_vertex_points[cont_v_out+5] = ponto2[2];
						out_vertex_points[cont_v_out+6] = ponto3[0];
						out_vertex_points[cont_v_out+7] = ponto3[1];
						out_vertex_points[cont_v_out+8] = ponto3[2];
						out_texture_points[cont_t_out] = texture_points[(fnum[1]-1)*2];
						out_texture_points[cont_t_out+1] = texture_points[(fnum[1]-1)*2+1];
						out_texture_points[cont_t_out+2] = texture_points[(fnum[3]-1)*2];
						out_texture_points[cont_t_out+3] = texture_points[(fnum[3]-1)*2+1];
						out_texture_points[cont_t_out+4] = texture_points[(fnum[5]-1)*2];
						out_texture_points[cont_t_out+5] = texture_points[(fnum[5]-1)*2+1];

						cont_v_out+=9;
						cont_t_out+=6;
						break;

					case V_SOLO: // Caso somente v...
						// Captura linha f e armazena em num.
						fscanf(objFile,"%d %d %d",&fnum[0],&fnum[1],&fnum[2]);

						// Atribui pontos do plano (triangulo)
						ponto1[0] = vertex_points[(fnum[0]-1)*3];
						ponto1[1] = vertex_points[(fnum[0]-1)*3+1];
						ponto1[2] = vertex_points[(fnum[0]-1)*3+2];
						ponto2[0] = vertex_points[(fnum[1]-1)*3];
						ponto2[1] = vertex_points[(fnum[1]-1)*3+1];
						ponto2[2] = vertex_points[(fnum[1]-1)*3+2];
						ponto3[0] = vertex_points[(fnum[2]-1)*3];
						ponto3[1] = vertex_points[(fnum[2]-1)*3+1];
						ponto3[2] = vertex_points[(fnum[2]-1)*3+2];

						// Atribui normal efetiva do plano (triangulo)
						MakeVector(ponto1,ponto2,vetor1);
						MakeVector(ponto1,ponto3,vetor2);
						ProdVet(vetor1,vetor2,vetor3);

						// Atribuição ordenada dos vértices dos triangulos.
						out_vertex_points[cont_v_out] = ponto1[0];
						out_vertex_points[cont_v_out+1] = ponto1[1];
						out_vertex_points[cont_v_out+2] = ponto1[2];
						out_vertex_points[cont_v_out+3] = ponto2[0];
						out_vertex_points[cont_v_out+4] = ponto2[1];
						out_vertex_points[cont_v_out+5] = ponto2[2];
						out_vertex_points[cont_v_out+6] = ponto3[0];
						out_vertex_points[cont_v_out+7] = ponto3[1];
						out_vertex_points[cont_v_out+8] = ponto3[2];

						cont_v_out+=9;
						break;
				}
		}
		
		PulaLinha(objFile);
	}
	fclose(objFile);

	printf("Vertice:\n");
	for(int i=0; i<cont_v_out; i+=3)
	{
		printf("%f %f %f\n",out_vertex_points[i],out_vertex_points[i+1],out_vertex_points[i+2]);
	}
	printf("%\n\n");

	printf("Textura:\n");
	for(int i=0; i<cont_t_out; i+=2)
	{
		printf("%f %f \n",out_texture_points[i],out_texture_points[i+1]);
	}
	
	
	
	*vertex_vector_size = cont_v_out;
	*texture_vector_size = cont_t_out;
	*in_vertex_points = out_vertex_points;
	*in_texture_points = out_texture_points;
}